{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torchvision import transforms as T\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "from pathlib import Path\n",
    "from collections import deque\n",
    "import random, datetime, os\n",
    "\n",
    "# Gym is an OpenAI toolkit for RL\n",
    "import gym\n",
    "from gym.spaces import Box\n",
    "from gym.wrappers import FrameStack\n",
    "\n",
    "# NES Emulator for OpenAI Gym\n",
    "from nes_py.wrappers import JoypadSpace\n",
    "\n",
    "# Super Mario environment for OpenAI Gym\n",
    "import gym_super_mario_bros\n",
    "\n",
    "from tensordict import TensorDict\n",
    "from torchrl.data import TensorDictReplayBuffer, LazyMemmapStorage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\gym\\envs\\registration.py:555: UserWarning: \u001b[33mWARN: The environment SuperMarioBros-1-1-v0 is out of date. You should consider upgrading to version `v3`.\u001b[0m\n",
      "  logger.warn(\n",
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\gym\\envs\\registration.py:627: UserWarning: \u001b[33mWARN: The environment creator metadata doesn't include `render_modes`, contains: ['render.modes', 'video.frames_per_second']\u001b[0m\n",
      "  logger.warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(240, 256, 3),\n",
      " 0,\n",
      " False,\n",
      " {'coins': 0, 'flag_get': False, 'life': 2, 'score': 0, 'stage': 1, 'status': 'small', 'time': 400, 'world': 1, 'x_pos': 40, 'y_pos': 79}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\gym\\utils\\passive_env_checker.py:233: DeprecationWarning: `np.bool8` is a deprecated alias for `np.bool_`.  (Deprecated NumPy 1.24)\n",
      "  if not isinstance(terminated, (bool, np.bool8)):\n"
     ]
    }
   ],
   "source": [
    "# Initialize Super Mario environment (in v0.26 change render mode to 'human' to see results on the screen)\n",
    "if gym.__version__ < '0.26':\n",
    "    original_env = gym_super_mario_bros.make(\"SuperMarioBros-1-1-v0\", new_step_api=True)\n",
    "else:\n",
    "    original_env = gym_super_mario_bros.make(\"SuperMarioBros-1-1-v0\", render_mode='rgb', apply_api_compatibility=True)\n",
    "\n",
    "# Limit the action-space to\n",
    "#   0. walk right\n",
    "#   1. jump right\n",
    "original_env = JoypadSpace(original_env, [[\"right\"], [\"right\", \"A\"]])\n",
    "\n",
    "original_env.reset()\n",
    "next_state, reward, done, trunc, info = original_env.step(action=0)\n",
    "print(f\"{next_state.shape},\\n {reward},\\n {done},\\n {info}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SkipFrame(gym.Wrapper):\n",
    "    def __init__(self, env, skip):\n",
    "        \"\"\"Return only every `skip`-th frame\"\"\"\n",
    "        super().__init__(env)\n",
    "        self._skip = skip\n",
    "\n",
    "    def step(self, action):\n",
    "        \"\"\"Repeat action, and sum reward\"\"\"\n",
    "        total_reward = 0.0\n",
    "        for i in range(self._skip):\n",
    "            # Accumulate reward and repeat the same action\n",
    "            obs, reward, done, trunk, info = self.env.step(action)\n",
    "            total_reward += reward\n",
    "            if done:\n",
    "                break\n",
    "        return obs, total_reward, done, trunk, info\n",
    "\n",
    "\n",
    "class GrayScaleObservation(gym.ObservationWrapper):\n",
    "    def __init__(self, env):\n",
    "        super().__init__(env)\n",
    "        obs_shape = self.observation_space.shape[:2]\n",
    "        self.observation_space = Box(low=0, high=255, shape=obs_shape, dtype=np.uint8)\n",
    "\n",
    "    def permute_orientation(self, observation):\n",
    "        # permute [H, W, C] array to [C, H, W] tensor\n",
    "        observation = np.transpose(observation, (2, 0, 1))\n",
    "        observation = torch.tensor(observation.copy(), dtype=torch.float)\n",
    "        return observation\n",
    "\n",
    "    def observation(self, observation):\n",
    "        observation = self.permute_orientation(observation)\n",
    "        transform = T.Grayscale()\n",
    "        observation = transform(observation)\n",
    "        return observation\n",
    "\n",
    "\n",
    "class ResizeObservation(gym.ObservationWrapper):\n",
    "    def __init__(self, env, shape):\n",
    "        super().__init__(env)\n",
    "        if isinstance(shape, int):\n",
    "            self.shape = (shape, shape)\n",
    "        else:\n",
    "            self.shape = tuple(shape)\n",
    "\n",
    "        obs_shape = self.shape + self.observation_space.shape[2:]\n",
    "        self.observation_space = Box(low=0, high=255, shape=obs_shape, dtype=np.uint8)\n",
    "\n",
    "    def observation(self, observation):\n",
    "        transforms = T.Compose(\n",
    "            [T.Resize(self.shape, antialias=True), T.Normalize(0, 255)]\n",
    "        )\n",
    "        observation = transforms(observation).squeeze(0)\n",
    "        return observation\n",
    "\n",
    "# Apply Wrappers to environment\n",
    "skip_frames = 4\n",
    "env = SkipFrame(original_env,skip=skip_frames)\n",
    "env = GrayScaleObservation(env)\n",
    "env = ResizeObservation(env, shape=84)\n",
    "if gym.__version__ < '0.26':\n",
    "    env = FrameStack(env, num_stack=4, new_step_api=True)\n",
    "else:\n",
    "    env = FrameStack(env, num_stack=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MarioNet(nn.Module):\n",
    "    \"\"\"mini CNN structure\n",
    "  input -> (conv2d + relu) x 3 -> flatten -> (dense + relu) x 2 -> output\n",
    "  \"\"\"\n",
    "\n",
    "    def __init__(self, input_dim, output_dim):\n",
    "        super().__init__()\n",
    "        c, h, w = input_dim\n",
    "\n",
    "        if h != 84:\n",
    "            raise ValueError(f\"Expecting input height: 84, got: {h}\")\n",
    "        if w != 84:\n",
    "            raise ValueError(f\"Expecting input width: 84, got: {w}\")\n",
    "\n",
    "        self.online = self.__build_cnn(c, output_dim)\n",
    "\n",
    "        self.target = self.__build_cnn(c, output_dim)\n",
    "        self.target.load_state_dict(self.online.state_dict())\n",
    "\n",
    "        # Q_target parameters are frozen.\n",
    "        for p in self.target.parameters():\n",
    "            p.requires_grad = False\n",
    "\n",
    "    def forward(self, input, model):\n",
    "        if model == \"online\":\n",
    "            return self.online(input)\n",
    "        elif model == \"target\":\n",
    "            return self.target(input)\n",
    "\n",
    "    def __build_cnn(self, c, output_dim):\n",
    "        return nn.Sequential(\n",
    "            nn.Conv2d(in_channels=c, out_channels=32, kernel_size=8, stride=4),\n",
    "            nn.ReLU(),\n",
    "            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=4, stride=2),\n",
    "            nn.ReLU(),\n",
    "            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1),\n",
    "            nn.ReLU(),\n",
    "            nn.Flatten(),\n",
    "            nn.Linear(3136, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(512, output_dim),\n",
    "        )\n",
    "class Mario:\n",
    "    def __init__(self, state_dim, action_dim, save_dir):\n",
    "        pass\n",
    "\n",
    "    def act(self, state):\n",
    "        \"\"\"Given a state, choose an epsilon-greedy action\"\"\"\n",
    "        pass\n",
    "\n",
    "    def cache(self, experience):\n",
    "        \"\"\"Add the experience to memory\"\"\"\n",
    "        pass\n",
    "\n",
    "    def recall(self):\n",
    "        \"\"\"Sample experiences from memory\"\"\"\n",
    "        pass\n",
    "\n",
    "    def learn(self):\n",
    "        \"\"\"Update online action value (Q) function with a batch of experiences\"\"\"\n",
    "        pass\n",
    "class Mario(Mario):\n",
    "    def __init__(self, state_dim, action_dim, save_dir):\n",
    "        super().__init__(state_dim, action_dim, save_dir)\n",
    "\n",
    "        self.state_dim = state_dim\n",
    "        self.action_dim = action_dim\n",
    "        self.save_dir = save_dir\n",
    "\n",
    "        self.device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "\n",
    "        # Mario's DNN to predict the most optimal action - we implement this in the Learn section\n",
    "        self.net = MarioNet(self.state_dim, self.action_dim).float()\n",
    "        self.net = self.net.to(device=self.device)\n",
    "\n",
    "        self.exploration_rate = 1\n",
    "        self.exploration_rate_decay = 0.99999975\n",
    "        self.exploration_rate_min = 0.1\n",
    "        self.curr_step = 0\n",
    "\n",
    "        self.save_every = 5e5  # no. of experiences between saving Mario Net\n",
    "        self.memory = TensorDictReplayBuffer(storage=LazyMemmapStorage(100000, device=torch.device(\"cpu\")))\n",
    "        self.batch_size = 32\n",
    "        self.gamma = 0.9\n",
    "        self.optimizer = torch.optim.Adam(self.net.parameters(), lr=0.00025)\n",
    "        self.loss_fn = torch.nn.SmoothL1Loss()\n",
    "        self.burnin = 1e4  # min. experiences before training\n",
    "        self.learn_every = 3  # no. of experiences between updates to Q_online\n",
    "        self.sync_every = 1e4  # no. of experiences between Q_target & Q_online sync\n",
    "    def act(self, state):\n",
    "        \"\"\"\n",
    "    Given a state, choose an epsilon-greedy action and update value of step.\n",
    "\n",
    "    Inputs:\n",
    "    state(``LazyFrame``): A single observation of the current state, dimension is (state_dim)\n",
    "    Outputs:\n",
    "    ``action_idx`` (``int``): An integer representing which action Mario will perform\n",
    "    \"\"\"\n",
    "        # EXPLORE\n",
    "        if np.random.rand() < self.exploration_rate:\n",
    "            action_idx = np.random.randint(self.action_dim)\n",
    "\n",
    "        # EXPLOIT\n",
    "        else:\n",
    "            state = state[0].__array__() if isinstance(state, tuple) else state.__array__()\n",
    "            state = torch.tensor(state, device=self.device).unsqueeze(0)\n",
    "            action_values = self.net(state, model=\"online\")\n",
    "            action_idx = torch.argmax(action_values, axis=1).item()\n",
    "\n",
    "        # decrease exploration_rate\n",
    "        self.exploration_rate *= self.exploration_rate_decay\n",
    "        self.exploration_rate = max(self.exploration_rate_min, self.exploration_rate)\n",
    "\n",
    "        # increment step\n",
    "        self.curr_step += 1\n",
    "        return action_idx\n",
    "    def cache(self, state, next_state, action, reward, done):\n",
    "        \"\"\"\n",
    "        Store the experience to self.memory (replay buffer)\n",
    "\n",
    "        Inputs:\n",
    "        state (``LazyFrame``),\n",
    "        next_state (``LazyFrame``),\n",
    "        action (``int``),\n",
    "        reward (``float``),\n",
    "        done(``bool``))\n",
    "        \"\"\"\n",
    "        def first_if_tuple(x):\n",
    "            return x[0] if isinstance(x, tuple) else x\n",
    "        state = first_if_tuple(state).__array__()\n",
    "        next_state = first_if_tuple(next_state).__array__()\n",
    "\n",
    "        state = torch.tensor(state)\n",
    "        next_state = torch.tensor(next_state)\n",
    "        action = torch.tensor([action])\n",
    "        reward = torch.tensor([reward])\n",
    "        done = torch.tensor([done])\n",
    "\n",
    "        # self.memory.append((state, next_state, action, reward, done,))\n",
    "        self.memory.add(TensorDict({\"state\": state, \"next_state\": next_state, \"action\": action, \"reward\": reward, \"done\": done}, batch_size=[]))\n",
    "\n",
    "    def recall(self):\n",
    "        \"\"\"\n",
    "        Retrieve a batch of experiences from memory\n",
    "        \"\"\"\n",
    "        batch = self.memory.sample(self.batch_size).to(self.device)\n",
    "        state, next_state, action, reward, done = (batch.get(key) for key in (\"state\", \"next_state\", \"action\", \"reward\", \"done\"))\n",
    "        return state, next_state, action.squeeze(), reward.squeeze(), done.squeeze()\n",
    "    def td_estimate(self, state, action):\n",
    "        current_Q = self.net(state, model=\"online\")[\n",
    "            np.arange(0, self.batch_size), action\n",
    "        ]  # Q_online(s,a)\n",
    "        return current_Q\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def td_target(self, reward, next_state, done):\n",
    "        next_state_Q = self.net(next_state, model=\"online\")\n",
    "        best_action = torch.argmax(next_state_Q, axis=1)\n",
    "        next_Q = self.net(next_state, model=\"target\")[\n",
    "            np.arange(0, self.batch_size), best_action\n",
    "        ]\n",
    "        return (reward + (1 - done.float()) * self.gamma * next_Q).float()\n",
    "    def update_Q_online(self, td_estimate, td_target):\n",
    "        loss = self.loss_fn(td_estimate, td_target)\n",
    "        self.optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        self.optimizer.step()\n",
    "        return loss.item()\n",
    "\n",
    "    def sync_Q_target(self):\n",
    "        self.net.target.load_state_dict(self.net.online.state_dict())\n",
    "    def learn(self):\n",
    "        if self.curr_step % self.sync_every == 0:\n",
    "            self.sync_Q_target()\n",
    "\n",
    "        if self.curr_step % self.save_every == 0:\n",
    "            self.save()\n",
    "\n",
    "        if self.curr_step < self.burnin:\n",
    "            return None, None\n",
    "\n",
    "        if self.curr_step % self.learn_every != 0:\n",
    "            return None, None\n",
    "\n",
    "        # Sample from memory\n",
    "        state, next_state, action, reward, done = self.recall()\n",
    "\n",
    "        # Get TD Estimate\n",
    "        td_est = self.td_estimate(state, action)\n",
    "\n",
    "        # Get TD Target\n",
    "        td_tgt = self.td_target(reward, next_state, done)\n",
    "\n",
    "        # Backpropagate loss through Q_online\n",
    "        loss = self.update_Q_online(td_est, td_tgt)\n",
    "\n",
    "        return (td_est.mean().item(), loss)\n",
    "    def save(self):\n",
    "        save_path = (\n",
    "            self.save_dir / f\"mario_net_{int(self.curr_step // self.save_every)}.chkpt\"\n",
    "        )\n",
    "        torch.save(\n",
    "            dict(model=self.net.state_dict(), exploration_rate=self.exploration_rate),\n",
    "            save_path,\n",
    "        )\n",
    "        print(f\"MarioNet saved to {save_path} at step {self.curr_step}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import time, datetime\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "class MetricLogger:\n",
    "    def __init__(self, save_dir):\n",
    "        self.save_log = save_dir / \"log\"\n",
    "        with open(self.save_log, \"w\") as f:\n",
    "            f.write(\n",
    "                f\"{'Episode':>8}{'Step':>8}{'Epsilon':>10}{'MeanReward':>15}\"\n",
    "                f\"{'MeanLength':>15}{'MeanLoss':>15}{'MeanQValue':>15}\"\n",
    "                f\"{'TimeDelta':>15}{'Time':>20}\\n\"\n",
    "            )\n",
    "        self.ep_rewards_plot = save_dir / \"reward_plot.jpg\"\n",
    "        self.ep_lengths_plot = save_dir / \"length_plot.jpg\"\n",
    "        self.ep_avg_losses_plot = save_dir / \"loss_plot.jpg\"\n",
    "        self.ep_avg_qs_plot = save_dir / \"q_plot.jpg\"\n",
    "\n",
    "        # History metrics\n",
    "        self.ep_rewards = []\n",
    "        self.ep_lengths = []\n",
    "        self.ep_avg_losses = []\n",
    "        self.ep_avg_qs = []\n",
    "\n",
    "        # Moving averages, added for every call to record()\n",
    "        self.moving_avg_ep_rewards = []\n",
    "        self.moving_avg_ep_lengths = []\n",
    "        self.moving_avg_ep_avg_losses = []\n",
    "        self.moving_avg_ep_avg_qs = []\n",
    "\n",
    "        # Current episode metric\n",
    "        self.init_episode()\n",
    "\n",
    "        # Timing\n",
    "        self.record_time = time.time()\n",
    "\n",
    "    def log_step(self, reward, loss, q):\n",
    "        self.curr_ep_reward += reward\n",
    "        self.curr_ep_length += 1\n",
    "        if loss:\n",
    "            self.curr_ep_loss += loss\n",
    "            self.curr_ep_q += q\n",
    "            self.curr_ep_loss_length += 1\n",
    "\n",
    "    def log_episode(self):\n",
    "        \"Mark end of episode\"\n",
    "        self.ep_rewards.append(self.curr_ep_reward)\n",
    "        self.ep_lengths.append(self.curr_ep_length)\n",
    "        if self.curr_ep_loss_length == 0:\n",
    "            ep_avg_loss = 0\n",
    "            ep_avg_q = 0\n",
    "        else:\n",
    "            ep_avg_loss = np.round(self.curr_ep_loss / self.curr_ep_loss_length, 5)\n",
    "            ep_avg_q = np.round(self.curr_ep_q / self.curr_ep_loss_length, 5)\n",
    "        self.ep_avg_losses.append(ep_avg_loss)\n",
    "        self.ep_avg_qs.append(ep_avg_q)\n",
    "\n",
    "        self.init_episode()\n",
    "\n",
    "    def init_episode(self):\n",
    "        self.curr_ep_reward = 0.0\n",
    "        self.curr_ep_length = 0\n",
    "        self.curr_ep_loss = 0.0\n",
    "        self.curr_ep_q = 0.0\n",
    "        self.curr_ep_loss_length = 0\n",
    "\n",
    "    def record(self, episode, epsilon, step):\n",
    "        mean_ep_reward = np.round(np.mean(self.ep_rewards[-100:]), 3)\n",
    "        mean_ep_length = np.round(np.mean(self.ep_lengths[-100:]), 3)\n",
    "        mean_ep_loss = np.round(np.mean(self.ep_avg_losses[-100:]), 3)\n",
    "        mean_ep_q = np.round(np.mean(self.ep_avg_qs[-100:]), 3)\n",
    "        self.moving_avg_ep_rewards.append(mean_ep_reward)\n",
    "        self.moving_avg_ep_lengths.append(mean_ep_length)\n",
    "        self.moving_avg_ep_avg_losses.append(mean_ep_loss)\n",
    "        self.moving_avg_ep_avg_qs.append(mean_ep_q)\n",
    "\n",
    "        last_record_time = self.record_time\n",
    "        self.record_time = time.time()\n",
    "        time_since_last_record = np.round(self.record_time - last_record_time, 3)\n",
    "\n",
    "        print(\n",
    "            f\"Episode {episode} - \"\n",
    "            f\"Step {step} - \"\n",
    "            f\"Epsilon {epsilon} - \"\n",
    "            f\"Mean Reward {mean_ep_reward} - \"\n",
    "            f\"Mean Length {mean_ep_length} - \"\n",
    "            f\"Mean Loss {mean_ep_loss} - \"\n",
    "            f\"Mean Q Value {mean_ep_q} - \"\n",
    "            f\"Time Delta {time_since_last_record} - \"\n",
    "            f\"Time {datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')}\"\n",
    "        )\n",
    "\n",
    "        with open(self.save_log, \"a\") as f:\n",
    "            f.write(\n",
    "                f\"{episode:8d}{step:8d}{epsilon:10.3f}\"\n",
    "                f\"{mean_ep_reward:15.3f}{mean_ep_length:15.3f}{mean_ep_loss:15.3f}{mean_ep_q:15.3f}\"\n",
    "                f\"{time_since_last_record:15.3f}\"\n",
    "                f\"{datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S'):>20}\\n\"\n",
    "            )\n",
    "\n",
    "        for metric in [\"ep_lengths\", \"ep_avg_losses\", \"ep_avg_qs\", \"ep_rewards\"]:\n",
    "            plt.clf()\n",
    "            plt.plot(getattr(self, f\"moving_avg_{metric}\"), label=f\"moving_avg_{metric}\")\n",
    "            plt.legend()\n",
    "            plt.savefig(getattr(self, f\"{metric}_plot\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "use_cuda = torch.cuda.is_available()\n",
    "print(f\"Using CUDA: {use_cuda}\")\n",
    "print()\n",
    "save_dir = Path(\"checkpoints\") / datetime.datetime.now().strftime(\"%Y-%m-%dT%H-%M-%S\")\n",
    "save_dir.mkdir(parents=True)\n",
    "mario = Mario(state_dim=(4, 84, 84), action_dim=env.action_space.n, save_dir=save_dir)\n",
    "logger = MetricLogger(save_dir)\n",
    "episodes = 40"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 158,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Episode 0 - Step 3080182 - Epsilon 0.1 - Mean Reward 1133.75 - Mean Length 210.35 - Mean Loss 1.457 - Mean Q Value 55.532 - Time Delta 46030.461 - Time 2025-06-13T09:03:46\n",
      "Episode 20 - Step 3084330 - Epsilon 0.1 - Mean Reward 1129.15 - Mean Length 205.47 - Mean Loss 1.447 - Mean Q Value 55.838 - Time Delta 73.577 - Time 2025-06-13T09:04:59\n",
      "Episode 40 - Step 3088216 - Epsilon 0.1 - Mean Reward 1126.72 - Mean Length 206.57 - Mean Loss 1.442 - Mean Q Value 56.1 - Time Delta 69.248 - Time 2025-06-13T09:06:08\n",
      "Episode 60 - Step 3092210 - Epsilon 0.1 - Mean Reward 1098.45 - Mean Length 197.83 - Mean Loss 1.45 - Mean Q Value 56.343 - Time Delta 74.873 - Time 2025-06-13T09:07:23\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\xchen\\AppData\\Local\\Temp\\5\\ipykernel_34192\\960009692.py:137: DeprecationWarning: In future, it will be an error for 'np.bool_' scalars to be interpreted as an index\n",
      "  done = torch.tensor([done])\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Episode 80 - Step 3097053 - Epsilon 0.1 - Mean Reward 1149.85 - Mean Length 202.75 - Mean Loss 1.456 - Mean Q Value 56.557 - Time Delta 87.531 - Time 2025-06-13T09:08:51\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\xchen\\AppData\\Local\\Temp\\5\\ipykernel_34192\\960009692.py:137: DeprecationWarning: In future, it will be an error for 'np.bool_' scalars to be interpreted as an index\n",
      "  done = torch.tensor([done])\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Episode 100 - Step 3100599 - Epsilon 0.1 - Mean Reward 1157.49 - Mean Length 204.17 - Mean Loss 1.46 - Mean Q Value 56.657 - Time Delta 64.586 - Time 2025-06-13T09:09:55\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\xchen\\AppData\\Local\\Temp\\5\\ipykernel_34192\\960009692.py:137: DeprecationWarning: In future, it will be an error for 'np.bool_' scalars to be interpreted as an index\n",
      "  done = torch.tensor([done])\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[158], line 19\u001b[0m\n\u001b[0;32m     16\u001b[0m mario\u001b[38;5;241m.\u001b[39mcache(state, next_state, action, reward, done)\n\u001b[0;32m     18\u001b[0m \u001b[38;5;66;03m# Learn\u001b[39;00m\n\u001b[1;32m---> 19\u001b[0m q, loss \u001b[38;5;241m=\u001b[39m \u001b[43mmario\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlearn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     21\u001b[0m \u001b[38;5;66;03m# Logging\u001b[39;00m\n\u001b[0;32m     22\u001b[0m logger\u001b[38;5;241m.\u001b[39mlog_step(reward, loss, q)\n",
      "Cell \u001b[1;32mIn[8], line 192\u001b[0m, in \u001b[0;36mMario.learn\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m    189\u001b[0m td_est \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtd_estimate(state, action)\n\u001b[0;32m    191\u001b[0m \u001b[38;5;66;03m# Get TD Target\u001b[39;00m\n\u001b[1;32m--> 192\u001b[0m td_tgt \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtd_target\u001b[49m\u001b[43m(\u001b[49m\u001b[43mreward\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnext_state\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdone\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    194\u001b[0m \u001b[38;5;66;03m# Backpropagate loss through Q_online\u001b[39;00m\n\u001b[0;32m    195\u001b[0m loss \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mupdate_Q_online(td_est, td_tgt)\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\utils\\_contextlib.py:116\u001b[0m, in \u001b[0;36mcontext_decorator.<locals>.decorate_context\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m    113\u001b[0m \u001b[38;5;129m@functools\u001b[39m\u001b[38;5;241m.\u001b[39mwraps(func)\n\u001b[0;32m    114\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mdecorate_context\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m    115\u001b[0m     \u001b[38;5;28;01mwith\u001b[39;00m ctx_factory():\n\u001b[1;32m--> 116\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m func(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "Cell \u001b[1;32mIn[8], line 159\u001b[0m, in \u001b[0;36mMario.td_target\u001b[1;34m(self, reward, next_state, done)\u001b[0m\n\u001b[0;32m    157\u001b[0m next_state_Q \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mnet(next_state, model\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124monline\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m    158\u001b[0m best_action \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39margmax(next_state_Q, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m--> 159\u001b[0m next_Q \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnet\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnext_state\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtarget\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m[\n\u001b[0;32m    160\u001b[0m     np\u001b[38;5;241m.\u001b[39marange(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbatch_size), best_action\n\u001b[0;32m    161\u001b[0m ]\n\u001b[0;32m    162\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (reward \u001b[38;5;241m+\u001b[39m (\u001b[38;5;241m1\u001b[39m \u001b[38;5;241m-\u001b[39m done\u001b[38;5;241m.\u001b[39mfloat()) \u001b[38;5;241m*\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgamma \u001b[38;5;241m*\u001b[39m next_Q)\u001b[38;5;241m.\u001b[39mfloat()\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\module.py:1751\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1749\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[0;32m   1750\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m-> 1751\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\module.py:1762\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1757\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[0;32m   1758\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[0;32m   1759\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[0;32m   1760\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[0;32m   1761\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[1;32m-> 1762\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m forward_call(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m   1764\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m   1765\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
      "Cell \u001b[1;32mIn[8], line 28\u001b[0m, in \u001b[0;36mMarioNet.forward\u001b[1;34m(self, input, model)\u001b[0m\n\u001b[0;32m     26\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39monline(\u001b[38;5;28minput\u001b[39m)\n\u001b[0;32m     27\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m model \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtarget\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m---> 28\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtarget\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\module.py:1751\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1749\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[0;32m   1750\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m-> 1751\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\module.py:1762\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1757\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[0;32m   1758\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[0;32m   1759\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[0;32m   1760\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[0;32m   1761\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[1;32m-> 1762\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m forward_call(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m   1764\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m   1765\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\container.py:240\u001b[0m, in \u001b[0;36mSequential.forward\u001b[1;34m(self, input)\u001b[0m\n\u001b[0;32m    238\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m):\n\u001b[0;32m    239\u001b[0m     \u001b[38;5;28;01mfor\u001b[39;00m module \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m:\n\u001b[1;32m--> 240\u001b[0m         \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mmodule\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m    241\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28minput\u001b[39m\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\module.py:1751\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1749\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[0;32m   1750\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m-> 1751\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\module.py:1762\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m   1757\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[0;32m   1758\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[0;32m   1759\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[0;32m   1760\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[0;32m   1761\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[1;32m-> 1762\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m forward_call(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m   1764\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m   1765\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\conv.py:554\u001b[0m, in \u001b[0;36mConv2d.forward\u001b[1;34m(self, input)\u001b[0m\n\u001b[0;32m    553\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[1;32m--> 554\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_conv_forward\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbias\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\torch\\nn\\modules\\conv.py:549\u001b[0m, in \u001b[0;36mConv2d._conv_forward\u001b[1;34m(self, input, weight, bias)\u001b[0m\n\u001b[0;32m    537\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpadding_mode \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mzeros\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m    538\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m F\u001b[38;5;241m.\u001b[39mconv2d(\n\u001b[0;32m    539\u001b[0m         F\u001b[38;5;241m.\u001b[39mpad(\n\u001b[0;32m    540\u001b[0m             \u001b[38;5;28minput\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_reversed_padding_repeated_twice, mode\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpadding_mode\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m    547\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgroups,\n\u001b[0;32m    548\u001b[0m     )\n\u001b[1;32m--> 549\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mF\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconv2d\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    550\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbias\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstride\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpadding\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdilation\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgroups\u001b[49m\n\u001b[0;32m    551\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjAAAAGdCAYAAAAMm0nCAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAkR1JREFUeJztnQeYVOX1xg/bC+zSm3RFKQIiKKJiVIhYoxF7T4wmRmPLX41RibHHqLFGo4ktYokmdsUCNgSpgogIKL3X3WV32T7/53wz5853v7l35s7s1N339zzL7JSduXfuMN9739Pa+Hw+HwEAAAAAZBBZqd4AAAAAAIBogYABAAAAQMYBAQMAAACAjAMCBgAAAAAZBwQMAAAAADIOCBgAAAAAZBwQMAAAAADIOCBgAAAAAJBx5FALpampiTZu3Ejt2rWjNm3apHpzAAAAAOAB7q+7e/du6tmzJ2VlZbU+AcPipXfv3qneDAAAAADEwLp166hXr16tT8Cw8yJvQElJSao3BwAAAAAeqKioUAaErOOtTsBI2IjFCwQMAAAAkFlESv9AEi8AAAAAMg4IGAAAAABkHBAwAAAAAMg4WmwOjNdSrYaGBmpsbEz1pgDQYsnOzqacnBy0MwAAxJVWK2Dq6upo06ZNVF1dnepNAaDFU1RURD169KC8vLxUbwoAoIXQKgUMN7lbtWqVOjPkRjn8pYqzQwAS43LyycK2bdvU/7mBAweGbUwFAABeaZUChr9QWcRwnTmfGQIAEkdhYSHl5ubSmjVr1P+9goKCVG8SAKAF0KpPhXAmCEBywP81AEC8wbcKAAAAADIOCBgAAAAAZBwQMCAst956Kx1wwAGp3gyQYp599llq3759qjcDAAAsIGBAWP7v//6Ppk2blurNAAAAAGxAwICwtG3bljp16pTqzWjxcHVOOpAu2wFAa+b1r9fTjBXbU70ZaQ8ETKBXRXVdQ0p++LW9cuSRR9Lvfvc7uvrqq6lDhw7UrVs3euqpp6iqqop+8YtfqNHj++yzD73//vvW33z22Wd08MEHU35+vmok9oc//EF1H2aefPJJ1QeHS8p1Tj75ZPrlL3/pGEK66KKL6JRTTqH77rtPPR+Lm8svv5zq6+utx3CDwBNOOEGVz/bv359efPFF6tevHz344IOe9vOBBx6gYcOGUXFxsSp1/+1vf0uVlZXWmHV+Xn0fmddff13tvzQmnDlzptpuLtkdPXo0vfHGG6rXz8KFCz1tw7fffkvHHXecEnD8Pp9//vm0fft227G44oor1E9paSl17tyZbrnlFs/Hk9+P22+/nS644AI1Lf3SSy9Vt8+YMYPGjRun9pH3/corr1THl3n00Udp//33t55D9umJJ56wbpswYQLdfPPN6vcff/xRHUveft6Pgw46iD7++GNP28Ehoz59+qg2Az//+c9px44dtr9btGgRHXXUUeo9578bNWoUzZs3z9O+AwDcWbezmq55ZRGd96/ZtGLL7lRvTlrTKvvAmOypb6Qhkz9IyWt/d9tEKsrzfhiee+45uv7662nOnDn0yiuv0GWXXaYWb15k/vjHP9Lf/vY3tdiuXbuWdu3aRccff7wSHc8//zx9//33dMkll6hFnYXJ6aefrgTRJ598QuPHj1fPv3PnTpo6dSq99957rtvAj2fxwpc//PADnXnmmUos8HMzvBjyYv/pp5+q/h/XXnstbd26NaqS24cffliJn5UrVyoBw/v897//XS2WJ554ohJFLDCEKVOmKGHFCy6LnJNOOkntOz+O+4+w6PNKWVkZHX300fSrX/1KvZ979uyhG264gc444wyaPn267VhcfPHF6ljw4s2LPy/68j5EgkXg5MmT6U9/+pMlOI499li644476Omnn1bN30QkPfPMM/STn/xECRq+vUuXLkqcsnDi9/k3v/mNEpGzZs1SIpVh0cfvwZ133qkELH8G+H1ZtmyZ2k637Zg9e7bar7vvvlu9p/x5kPuEc889l0aOHEmPP/64agjJwpCPNQCgeaze4T9hYZ6ZuZru+vmwlG5POtPGF40FkEHwIsZnxuXl5WrR06mpqVFdQXmB5MWcnZBMEDB81s9zm7744gt1nX/nfTz11FPV4sRs3rxZiQteyN5++23673//S0uXLrU6DbMI4MWY3xcWCrxAsYvyr3/9y3Jl/vznP9O6devU/Sx0+ExfnAsWQ7xg8mLLCxfDCzs/9uWXX1YiafDgwTR37lzlfDAscrgDK4uBaISE8Nprr6kFWhwQ3h4WaVu2bLEEC7sMLORYALAjwS7E+vXrraZp//znP5Ww+PrrryMmJbOA4Pf4gw+Cnwl+LnZEePHfd9991bFgUbZkyRLrvWXh8NZbb9F3330XcZ/Y+WABwNsssGDi9/Qf//iHdRs7Mixc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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "episodes = 4000\n",
    "for e in range(episodes):\n",
    "\n",
    "    state = env.reset()\n",
    "\n",
    "    # Play the game!\n",
    "    while True:\n",
    "\n",
    "        # Run agent on the state\n",
    "        action = mario.act(state)\n",
    "\n",
    "        # Agent performs action\n",
    "        next_state, reward, done, trunc, info = env.step(action)\n",
    "\n",
    "        # Remember\n",
    "        mario.cache(state, next_state, action, reward, done)\n",
    "\n",
    "        # Learn\n",
    "        q, loss = mario.learn()\n",
    "\n",
    "        # Logging\n",
    "        logger.log_step(reward, loss, q)\n",
    "\n",
    "        # Update state\n",
    "        state = next_state\n",
    "\n",
    "        # Check if end of game\n",
    "        if done or info[\"flag_get\"]:\n",
    "            break\n",
    "\n",
    "    logger.log_episode()\n",
    "\n",
    "    if (e % 20 == 0) or (e == episodes - 1):\n",
    "        logger.record(episode=e, epsilon=mario.exploration_rate, step=mario.curr_step)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 156,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\gym\\envs\\registration.py:555: UserWarning: \u001b[33mWARN: The environment SuperMarioBros-1-1-v0 is out of date. You should consider upgrading to version `v3`.\u001b[0m\n",
      "  logger.warn(\n",
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\gym\\envs\\registration.py:627: UserWarning: \u001b[33mWARN: The environment creator metadata doesn't include `render_modes`, contains: ['render.modes', 'video.frames_per_second']\u001b[0m\n",
      "  logger.warn(\n",
      "c:\\Users\\xchen\\anaconda3\\envs\\CX_RL\\lib\\site-packages\\gym\\utils\\passive_env_checker.py:233: DeprecationWarning: `np.bool8` is a deprecated alias for `np.bool_`.  (Deprecated NumPy 1.24)\n",
      "  if not isinstance(terminated, (bool, np.bool8)):\n"
     ]
    }
   ],
   "source": [
    "videos = []\n",
    "if gym.__version__ < '0.26':\n",
    "    original_env = gym_super_mario_bros.make(\"SuperMarioBros-1-1-v0\", new_step_api=True)\n",
    "else:\n",
    "    original_env = gym_super_mario_bros.make(\"SuperMarioBros-1-1-v0\", render_mode='rgb', apply_api_compatibility=True)\n",
    "original_env = JoypadSpace(original_env, [[\"right\"], [\"right\", \"A\"]])\n",
    "original_state = original_env.reset()\n",
    "state = env.reset()\n",
    "videos.append(original_state[0].copy())\n",
    "a = mario.exploration_rate\n",
    "mario.exploration_rate = -1\n",
    "while True:\n",
    "  action = mario.act(state)\n",
    "  mario.exploration_rate = -1\n",
    "  next_state, reward, done, trunc, info = env.step(action)\n",
    "  state = next_state\n",
    "  tmp = 0\n",
    "  for i in range(skip_frame):\n",
    "    tmp = original_env.step(action)[0]\n",
    "  videos.append(tmp.copy())\n",
    "  if done or info[\"flag_get\"]:\n",
    "    break\n",
    "videos = np.array(videos)\n",
    "original_env.close()\n",
    "mario.exploration_rate = a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {},
   "outputs": [],
   "source": [
    "videos = []\n",
    "state = env.reset()\n",
    "videos.append(state[0])\n",
    "a = mario.exploration_rate\n",
    "mario.exploration_rate = -1\n",
    "while True:\n",
    "  action = mario.act(state)\n",
    "  mario.exploration_rate = -1\n",
    "  next_state, reward, done, trunc, info = env.step(action)\n",
    "  state = next_state\n",
    "  tmp = 0\n",
    "  videos.append(state)\n",
    "  if done or info[\"flag_get\"]:\n",
    "    break\n",
    "videos = np.array(videos).transpose(0,2,3,1)\n",
    "original_env.close()\n",
    "mario.exploration_rate = a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 157,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "视频已保存至: a11.mp4\n"
     ]
    }
   ],
   "source": [
    "import imageio\n",
    "import numpy as np\n",
    "def frames_to_video_imageio(frames, output_path, fps=30):\n",
    "    \"\"\"使用imageio转换（简单但功能有限）\"\"\"\n",
    "    frames = np.array(frames)\n",
    "    with imageio.get_writer(output_path, fps=fps) as writer:\n",
    "        for frame in frames:\n",
    "            # 处理RGB格式\n",
    "            if len(frame.shape) == 3 and frame.shape[2] == 3:\n",
    "                frame = (frame * 255).astype(np.uint8) if frame.dtype == np.float32 else frame.astype(np.uint8)\n",
    "            # 处理灰度图\n",
    "            elif len(frame.shape) == 2:\n",
    "                frame = frame.astype(np.uint8)\n",
    "            writer.append_data(frame)\n",
    "    print(f\"视频已保存至: {output_path}\")\n",
    "frames_to_video_imageio(videos,'a11.mp4')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
